﻿/*
 * HyperStat.NET - The minimalist's static website compiler.
 * Copyright (C) 2012 Benjamin Brent
 * http://www.hyperstat.net/
 * http://hyperstat.codeplex.com/
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Reflection;
using System.Resources;
using System.Collections;
using System.Diagnostics;
using System.Net;
using NDesk.Options;

namespace HyperStat
{
    class Program
    {
        private StaticHtmlGenerator statGen;
        public static int verbosity = 0;
        private const string HELP_MESSAGE = "Try `HyperStat --help' for more information.";

        public static volatile bool serverMode = false;

        /// <summary>
        /// Entry  point.
        /// </summary>
        private void Run(string[] args)
        {
            bool    version        = false;
            bool    help           = false;
                    serverMode     = false;
            bool    init           = false;
            bool    init_e         = false;
            int     serverModePort = 0;
            string  prjDir         = null;
            string  outDir         = null;

            // Specify command-line options
            var p = new OptionSet () {
                { "i|init",      "create a new HyperStat project in the current directory",                            v  =>  init = v != null   },
                { "e|init-empty","create a skeleton HyperStat project in the current directory",                       v  =>  init_e = v != null },
                { "p=|project=", "{PATH} to the project directory",                                                    v  =>  prjDir = v         },
                { "o=|output=",  "{PATH} to the desired output directory (will be created if it doesn't exist)",       v  =>  outDir = v         },
                { "s:|server:",  "run in server mode on the specified {PORT} (default is 8080)",                  (int v) =>  { serverModePort = v; serverMode = true; } },                
                { "v|verbose",   "increase debug message verbosity",                                                   v  =>  ++verbosity        },
                { "V|version",   "display version information",                                                        v  =>  version = v != null},
                { "h|?|help",    "show this message and exit",                                                         v  =>  help = v != null   },
            };

            // Holds 'extra' options (any left over that don't fit the specified ones)
            List<string> extra;

            try
            {
                extra = p.Parse(args);
            }
            catch (OptionException e)
            {
                Console.Write("HyperStat: ");
                Console.WriteLine(e.Message);
                Console.WriteLine(HELP_MESSAGE);
                return;
            }

            // Allow `HyperStat init`
            if (extra.Contains("init"))
                init = true;

            // Show help
            if (help)
            {
                ShowHelp(p);
                return;
            }

            // Show version
            else if (version)
            {
                ShowVersion(p);
                return;
            }

            // Determine directory paths
            statGen = new StaticHtmlGenerator();
            statGen.Overwrite = true;

            if (prjDir == null)
            {
                // No proj dir given, try to use the current dir
                string cd = Environment.CurrentDirectory;
                if (File.Exists(Path.Combine(cd, StaticHtmlGenerator.PROJ_SETTINGS_FILE)))
                    statGen.ProjectDir = cd;
                else if(!init && !init_e)
                    throw new HyperStatException("No HyperStat project could be found!\r\n\r\n" + HELP_MESSAGE);
            }
            else
            {
                statGen.ProjectDir = prjDir.TrimEnd('\\').TrimEnd('/'); // remove trailing slashes
            }

            if (outDir == null)
            {
                // Try to use out subdir in proj dir
                if (statGen.ProjectDir != null)
                    statGen.OutputDir = Path.Combine(statGen.ProjectDir, StaticHtmlGenerator.DEFAULT_OUT_DIR);
            }
            else
            {
                statGen.OutputDir = outDir.TrimEnd('\\').TrimEnd('/'); // remove trailing slashes
            }

            // Init mode
            if (init || init_e)
            {
                Debug("Starting in init mode");

                // With sample
                if (!init_e)
                    InitCurrentDir(false);

                // Clean (without sample)
                else
                    InitCurrentDir(true);
            }

            // Web Server Mode
            else if (serverMode)
            {
                // Set the default port to 8080
                if (serverModePort == 0)
                    serverModePort = 8080;

                Debug("Starting in server mode (port: {0})", serverModePort);

                try
                {
                    // Start the web server
                    HyperServe hs = new HyperServe(statGen);
                    hs.Prefix = "http://+:" + serverModePort + "/";
                    hs.Start();

                    // Stay running until keypress
                    Console.WriteLine("Web server listening on port " + serverModePort);
                    Console.WriteLine("Press any key to stop...");
                    Console.ReadKey();

                    // Clean up temp files
                    string webTmp = Path.Combine(Path.GetTempPath(), StaticHtmlGenerator.TEMP_WEBROOT);
                    DirectoryInfo dirInfo = new DirectoryInfo(webTmp);
                    if (dirInfo.Exists)
                        dirInfo.Delete(true);

                    Environment.Exit(0);
                }
                catch (HttpListenerException ex)
                {
                    if (ex.ErrorCode == 5)
                    {
                        Console.WriteLine("ERROR: Elevation is required for web server mode.\r\nERROR: You must start HyperStat from an elevated command prompt.");
                        Console.WriteLine("ERROR: Press any key to exit...");
                        Console.ReadKey();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("The following exception occurred: " + ex.Message);
                    Environment.Exit(0);
                }
            }

            // Compiler mode
            else
            {
                Debug("Starting in compiler mode");

                statGen.Generate();
            }
        }

        /// <summary>
        /// Initialises the current directory as a project directory.
        /// </summary>
        private void InitCurrentDir(bool empty = false)
        {
            string cd = Environment.CurrentDirectory;
            Directory.CreateDirectory(Path.Combine(cd, StaticHtmlGenerator.CONTENT_DIR));
            Directory.CreateDirectory(Path.Combine(cd, StaticHtmlGenerator.IMG_DIR));
            Directory.CreateDirectory(Path.Combine(cd, StaticHtmlGenerator.JS_DIR));
            Directory.CreateDirectory(Path.Combine(cd, StaticHtmlGenerator.CSS_DIR));
            Directory.CreateDirectory(Path.Combine(cd, StaticHtmlGenerator.STYLE_DIR));

            // Write sample project
            if (!empty)
            {
                WriteResToFile("master", Path.Combine(cd, StaticHtmlGenerator.MASTER_TPL_FILE));
                WriteResToFile("settings", Path.Combine(cd, StaticHtmlGenerator.PROJ_SETTINGS_FILE));
                WriteResToFile("navigation", Path.Combine(cd, StaticHtmlGenerator.NAVIGATION_FILE));
                WriteResToFile("index", Path.Combine(cd, StaticHtmlGenerator.CONTENT_DIR + Path.DirectorySeparatorChar + "index.mkd"));
                WriteResToFile("hello", Path.Combine(cd, StaticHtmlGenerator.CONTENT_DIR + Path.DirectorySeparatorChar + "hello.html"));
                WriteResToFile("styles", Path.Combine(cd, StaticHtmlGenerator.CSS_DIR + Path.DirectorySeparatorChar + "styles.css"));
                WriteResToFile("favicon", Path.Combine(cd, StaticHtmlGenerator.IMG_DIR + Path.DirectorySeparatorChar + "favicon.ico"));
            }

            else
            {
                File.Create(Path.Combine(cd, StaticHtmlGenerator.MASTER_TPL_FILE)).Close();
                File.Create(Path.Combine(cd, StaticHtmlGenerator.PROJ_SETTINGS_FILE)).Close();
                File.Create(Path.Combine(cd, StaticHtmlGenerator.NAVIGATION_FILE)).Close();
            }
        }

        /// <summary>
        /// Writes the given manifest resource to the given path.
        /// </summary>
        private void WriteResToFile(string resName, string path)
        {
            byte[] resCtnt = (byte[]) SampleProjFiles.ResourceManager.GetObject(resName);
            File.WriteAllBytes(path, resCtnt);
        }

        /// <summary>
        /// Main entry point
        /// </summary>
        static void Main(string[] args)
        {
            try
            {
                Program prog = new Program();
                prog.Run(args);
            }
            catch (HyperStatException ex)
            {
                Console.WriteLine("ABORT: " + ex.Message);
            }
        }

        /// <summary>
        /// Displays the help and usage instructions.
        /// </summary>
        private void ShowHelp(OptionSet p)
        {
            Console.WriteLine("Usage: HyperStat [-v] -i");
            Console.WriteLine("       HyperStat [-v] -e");
            Console.WriteLine("       HyperStat [-v] [-p projDir] [-o outDir]");
            Console.WriteLine("       HyperStat [-v] -s[=PORT] [-p projDir]");
            Console.WriteLine("       HyperStat [OPTIONS]");
            Console.WriteLine("Creates a new HyperStat project, compiles the specified HyperStat project to static HTML, or serves the specified HyperStat project over HTTP.");
            Console.WriteLine("The current directory will be used where possible if a project directory is not specified.");
            Console.WriteLine();
            Console.WriteLine("Options:");
            p.WriteOptionDescriptions(Console.Out);
        }

        /// <summary>
        /// Displays version information.
        /// </summary>
        private void ShowVersion(OptionSet p)
        {
            Console.WriteLine("\r\nHyperStat.NET {0}\r\n", StaticHtmlGenerator.HS_VERSION);
        }

        /// <summary>
        /// Outputs a string to the console (in verbose mode only).
        /// </summary>
        public static void Debug(string format, params object[] args)
        {
            if (verbosity > 0)
            {
                Console.Write("# ");
                Console.WriteLine(format, args);
            }
        }

        /// <summary>
        /// Determines whether running on Linux.
        /// </summary>
        public static bool IsLinux
        {
            get
            {
                int p = (int)Environment.OSVersion.Platform;
                return (p == 4) || (p == 6) || (p == 128);
            }
        }
    }
}
